using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using System.ComponentModel;

namespace System.Html {

    /// <summary>
    /// Utility class used to cache cookies for multiple requests. Can also be used for authentication
    /// </summary>
    public class HTTPSiteWrapper {
        const string DEFAULT_CONTENT_TYPE = "application/x-www-form-urlencoded";
        const string DEFAULT_ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
        NetworkCredential credentials;
        CookieCollection cookies;
        Uri referrer;
        Uri home;
        string userAgent = "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15";
        Version useVersion = HttpVersion.Version11;
        /// <summary>
        /// Gets or sets the user agent to report to the web server
        /// </summary>
        public string UserAgent {
            get {
                return this.userAgent;
            }
            set {
                this.userAgent = value;
            }
        }

        public Version UseVersion {
            get {
                return useVersion;
            }
            set {
                useVersion = value;
            }
        }

        private int currentTimeOut = 30000;
        public int Timeout {
            get {
                return currentTimeOut;
            }
            set {
                currentTimeOut = value;
            }
        }

        private int maxAttempts = 3;
        public int MaxDownloadAttempts {
            get {
                return maxAttempts;
            }
            set {
                maxAttempts = value;
            }
        }

        /// <summary>
        /// The URI used to initilize the SiteWrapper
        /// </summary>
        public Uri Home {
            get {
                return home;
            }
        }

        /// <summary>
        /// Flag is false if the site has not been initilized
        /// </summary>
        public bool Initilized {
            get;
            protected set;
        }

        /// <summary>
        /// The content type to accept (e.g. text/html or image/jpg)
        /// </summary>
        public string Accept {
            get;
            set;
        }

        /// <summary>
        /// Initlize the wrapper with the first page of the site. This is usually the home page
        /// </summary>
        /// <param name="location">Uri to use for initlization</param>
        /// <param name="username">optional network credential username, use if site uses network auth method. Leave null or empty if not in use</param>
        /// <param name="password">optional passord</param>
        public HTTPSiteWrapper(Uri location, string username, string password) {
            this.home = location;
            if (!String.IsNullOrEmpty(username)) {
                if (!string.IsNullOrEmpty(password)) {
                    this.credentials = new NetworkCredential(username, password);
                }
            }
            Initilized = false;
            Accept = DEFAULT_ACCEPT;
            LocalEndPointIP = IPAddress.Any;
            LocalEndPointPort = 0;
        }

        /// <summary>
        /// Specifies the local ip addres to use to make the requests
        /// default value is IPAddress.Any
        /// </summary>
        public IPAddress LocalEndPointIP {
            get;
            set;
        }

        /// <summary>
        /// Specify the local port to use to make the requests
        /// default value is 0
        /// </summary>
        public int LocalEndPointPort {
            get;
            set;
        }

        /// <summary>
        /// Initlize the wrapper with the first page of the site. This is usually the home page
        /// </summary>
        /// <param name="location">Uri to use for initlization</param>
        /// <param name="location"></param>
        public HTTPSiteWrapper(Uri location) {
            this.home = location;
            Accept = DEFAULT_ACCEPT;
            Initilized = false;
            LocalEndPointIP = IPAddress.Any;
            LocalEndPointPort = 0;
        }

        private IPEndPoint BindIPEndPointCallback(ServicePoint servicePoint, IPEndPoint remoteEndPoint, int retryCount) {
            if (remoteEndPoint.AddressFamily != LocalEndPointIP.AddressFamily) {
                return null;
            }
            else {
                return new IPEndPoint(LocalEndPointIP, LocalEndPointPort);
            }
        }

        /// <summary>
        /// Request the url and expect a cookie in return
        /// </summary>
        /// <param name="expectCookie">Set to true if we're performing an auth and the site is supposed to respond with cookie information</param>
        /// <param name="postData">optional post data for forms authentication</param>
        /// <param name="postDataEncoding">optional encoding information for the posted data</param>
        /// <returns>Text data of the initial page</returns>
        public string Init(bool expectCookie, string postData, Encoding postDataEncoding) {
            //auth and get the cookie
            HttpWebRequest _site = (HttpWebRequest)WebRequest.Create(home);
            _site.ServicePoint.BindIPEndPointDelegate = new BindIPEndPoint(BindIPEndPointCallback);
            _site.ProtocolVersion = UseVersion;
            _site.Credentials = credentials;
            _site.AllowAutoRedirect = true;
            _site.Accept = Accept;
            _site.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
            _site.UserAgent = userAgent;
            _site.KeepAlive = true;
            _site.Timeout = Timeout;
            _site.CookieContainer = new CookieContainer();
            if (!String.IsNullOrEmpty(postData)) {
                StreamWriter _streamWriter = new StreamWriter(_site.GetRequestStream(), postDataEncoding);
                _streamWriter.Write(postData);
                _streamWriter.Flush();
                _streamWriter.Close();
            }

            HttpWebResponse _siteResponse = null;
            try {
                _siteResponse = (HttpWebResponse)_site.GetResponse();
            }
            catch (WebException webEx) {
                throw new WebException("Unable to obtain a cookie, as per the expectation.", webEx);
            }
            try {
                this.cookies = _siteResponse.Cookies;
                //this.cookie = _siteResponse.Headers["Set-Cookie"].Split(';')[0];
            }
            catch (Exception ex) {
                if (expectCookie) {
                    throw new WebException("Unable to obtain a cookie, as per the expectation.", ex);
                }
            }
            ///return the obtained data to the user as an option
            StreamReader _strReader = new StreamReader(_siteResponse.GetResponseStream(), System.Text.Encoding.GetEncoding(_siteResponse.CharacterSet));
            string _htmlData = _strReader.ReadToEnd();
            _strReader.Close();
            referrer = _siteResponse.ResponseUri;
            Initilized = true;
            return _htmlData;
        }

        /// <summary>
        /// Request the url and an option to expect a cookie in return
        /// </summary>
        /// <param name="expectCookie">Set to true if we're performing an auth and the site is supposed to respond with cookie information</param>
        /// <returns>Text data of the initial page</returns>
        public string Init(bool expectCookie) {
            return Init(expectCookie, string.Empty, null);
        }

        /// <summary>
        /// Request the url and expects a cookie in return
        /// </summary>
        /// <returns>Text data of the initial page</returns>
        public string Init() {
            return Init(true);
        }

        /// <summary>
        /// Returns a raw response object from a request. Usefull for getting binary information from a Uri
        /// </summary>
        /// <param name="url">Uri of the request</param>
        /// <param name="postData">optional post data to be sent with the request</param>
        /// <param name="postDataEncoding">optional encoding of the post data</param>
        /// <returns>HttpResponse Object with the response data</returns>
        public HttpWebResponse GetResponse(Uri url, string postData, string contentType, Encoding postDataEncoding) {
            if (Initilized && url.Host != this.home.Host) {
                throw new WebException("The url specified does not correspond with the url used to init the connection.");
            }
            HttpWebRequest _site = (HttpWebRequest)WebRequest.Create(url);

            _site.ProtocolVersion = UseVersion;
            _site.Credentials = credentials;
            _site.AllowAutoRedirect = true;
            _site.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
            _site.UserAgent = userAgent;
            _site.Accept = Accept;
            _site.Timeout = Timeout;
            _site.KeepAlive = true;
            _site.Accept = "*/*";
            if (referrer != null) {
                _site.Referer = referrer.ToString();
            }
            if (this.credentials != null) {
                _site.Credentials = this.credentials;
            }
            if (this.cookies != null) {
                _site.CookieContainer = new CookieContainer();
                _site.CookieContainer.Add(this.cookies);
            }
            _site.Timeout = Timeout;

            if (!String.IsNullOrEmpty(postData)) {
                _site.Method = "POST";
                _site.ContentType = contentType;
                StreamWriter _streamWriter = new StreamWriter(_site.GetRequestStream(), postDataEncoding);
                _streamWriter.Write(postData);
                _streamWriter.Flush();
                _streamWriter.Close();
            }
            HttpWebResponse _response = null;
            try {
                _response = (HttpWebResponse)_site.GetResponse();
                referrer = _response.ResponseUri;
            }
            catch (WebException webEx) {

            }
            return _response;
        }

        /// <summary>
        /// Returns a raw response object from a request. Usefull for getting binary information from a Uri
        /// </summary>
        /// <param name="url">Uri of the request</param>
        /// <param name="postData">optional post data to be sent with the request</param>
        /// <returns>HttpResponse Object with the response data</returns>
        public HttpWebResponse GetResponse(Uri url, string postData) {
            ///request with the default encoding
            return GetResponse(url, postData, DEFAULT_CONTENT_TYPE, Encoding.Default);
        }

        /// <summary>
        /// Returns a raw response object from a request. Usefull for getting binary information from a Uri
        /// </summary>
        /// <param name="url">Uri of the request</param>
        /// <returns>HttpResponse Object with the response data</returns>
        public HttpWebResponse GetResponse(Uri url) {
            return GetResponse(url, string.Empty);
        }

        /// <summary>
        /// Gets text data from Uri with optional post data and specified encoding
        /// </summary>
        /// <param name="url">Uri of the of the data </param>
        /// <param name="postData">urlencoded data to be posted</param>
        /// <param name="postDataEncoding">encoding of the post data</param>
        /// <returns>Text of the request</returns>
        public string GetPage(Uri url, string postData, string contentType, Encoding postDataEncoding) {
            int _downloadAttempts = 0;
            string _htmlData = string.Empty;
            while (_downloadAttempts < MaxDownloadAttempts) {
                _downloadAttempts++;
                HttpWebResponse _response = GetResponse(url, postData, contentType, postDataEncoding);
                if (_response != null) {
                    long _contentLength = _response.ContentLength;
                    StreamReader _strReader = new StreamReader(_response.GetResponseStream(), String.IsNullOrEmpty(_response.CharacterSet) ?
                        Encoding.Default : System.Text.Encoding.GetEncoding(_response.CharacterSet));
                    _htmlData = _strReader.ReadToEnd();
                    _strReader.Close();
                    //if the amount of data downloaded is equal to the amount expected
                    if (_contentLength > 0 && _htmlData.Length == _contentLength) {
                        break;
                    }
                    //of if the content length cannot be determined
                    else if (_contentLength == 0) {
                        break;
                    }
                }
            }
            //if the third attempt failed then just return what ever you got
            return _htmlData;
        }
        /// <summary>
        /// Get's text data from a Uri with optional post data and the system's default encoding
        /// </summary>
        /// <param name="url">Uri of the of the data </param>
        /// <param name="postData">urlencoded data to be posted</param>
        /// <returns>Text of the request</returns>
        public string GetPage(Uri url, string postData) {
            return GetPage(url, postData, DEFAULT_CONTENT_TYPE, Encoding.Default);
        }
        /// <summary>
        /// Get's the text data from a Uri with no post data
        /// </summary>
        /// <param name="url">Uri of the of the data </param>
        /// <returns>Text of the request</returns>
        public string GetPage(Uri url) {
            return GetPage(url, string.Empty);
        }

    }
}